package snapex.expense.controller;

import java.io.ByteArrayOutputStream;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import snapex.core.model.Expense;
import snapex.core.model.ExpenseDetail;
import snapex.core.model.InvoiceImage;
import snapex.core.model.Result;
import snapex.core.model.SearchParams;
import snapex.expense.service.IExpenseService;
import snapex.invoice.service.IInvoiceService;

@Slf4j
@RestController
public class ExportContoller {
	@Autowired
	private IExpenseService expenseService;
	
	@Autowired
	private IInvoiceService invoiceService;
	
	@ApiOperation(value="Download expense json file and invoice images")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "staffId", value = "staffId, e.g. 34052468", required = false, dataType = "String"),
		@ApiImplicitParam(name = "status", value = "status, e.g. 0 for Draft, 1 for Pending Approval", required = false, dataType = "String"),
		@ApiImplicitParam(name = "submittedDate", value = "Submitt Date e.g. 2020-01-10", required = false, dataType = "String")
	})
	@GetMapping(value="/expense/downloadall")
	@PreAuthorize("hasAuthority('R_O_EXP_Export')")
	public ResponseEntity<byte[]> downloadAll(@RequestParam(required=false) String staffId, @RequestParam(required=false)String expenseId,
			@RequestParam(required=false) Integer status, @RequestParam(required=false) Date submittedDate,  @RequestParam(required=false) String level) throws Exception {

		return downloadAllPost(searchParams(staffId, expenseId, status, submittedDate, level));		
	}

	@PostMapping(value="/expense/downloadall")
	@PreAuthorize("hasAuthority('R_O_EXP_Export')")
	public ResponseEntity<byte[]> downloadAllPost(SearchParams params) throws Exception {

		log.debug("****POST /expense/downloadall ");
		
		String filename = new StringBuffer("snapex-expense-").append(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd_HH_mm_ss")).append(".zip").toString();
		
		return ResponseEntity.ok()
				.contentType(MediaType.APPLICATION_OCTET_STREAM)
				.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename="+filename)
				.body(this.exportAll(params));		
	}


	@ApiOperation(value="Extract expense json file and invoice images")
	@PostMapping(value="/expense/extractall")
	@PreAuthorize("hasAuthority('R_O_EXP_Export')")
	public ResponseEntity<byte[]> extractdAllPost(@RequestBody SearchParams params) throws Exception {
		log.debug("****GET /expense/downloadall ");
		
		return ResponseEntity.ok()
				.contentType(MediaType.APPLICATION_OCTET_STREAM)
				.body(this.exportAll(params));	
	}

	@ApiOperation(value="Extract expense json file and invoice images")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "staffId", value = "staffId, e.g. 34052468", required = false, dataType = "String"),
		@ApiImplicitParam(name = "status", value = "status, e.g. 0 for Draft, 1 for Pending Approval", required = false, dataType = "String"),
		@ApiImplicitParam(name = "submittedDate", value = "Submitt Date e.g. 2020-01-10", required = false, dataType = "String")
	})
	@GetMapping(value="/expense/extractall")
	@PreAuthorize("hasAuthority('R_O_EXP_Export')")
	public ResponseEntity<byte[]> extractdAllGet(@RequestParam(required=false) String staffId, @RequestParam(required=false)String expenseId,
			@RequestParam(required=false) Integer status, @RequestParam(required=false) Date submittedDate,  @RequestParam(required=false) String level) throws Exception {
		
		return ResponseEntity.ok()
				.contentType(MediaType.APPLICATION_OCTET_STREAM)
				.body(this.exportAll(searchParams(staffId, expenseId, status, submittedDate, level)));	
	}
	
	private byte[] exportAll(SearchParams params) throws Exception {
		
		if(params.getStaffId() != null) {
			params.setUsername(DigestUtils.md5DigestAsHex(params.getStaffId().getBytes()));
		}
		
		log.debug("***Start to export*** {}",params);
		
		List<Expense> expenses = expenseService.exportExpenses(params).getData();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();			
		ZipOutputStream zipOs = new ZipOutputStream(baos);
		
		if(SearchParams.LEVEL_FULL.equalsIgnoreCase(params.getLevel())) {
			List<String> invoiceIds = new ArrayList<String>();
			for(Expense expense : expenses) {
				for(ExpenseDetail ed : expense.getPurposes()) {
					invoiceIds.add(ed.getInvoiceId());
				}
			}	

			if(!invoiceIds.isEmpty()) {
				List<InvoiceImage> images = invoiceService.getInvoiceImages(invoiceIds);					
				for(InvoiceImage image : images) {
					zipOs.putNextEntry(new ZipEntry(image.getInvoiceId() +"."+ image.getMime()));
					zipOs.write(image.getImage());
					zipOs.closeEntry();	
				}
			}
		}
		
		ObjectMapper mapper = new ObjectMapper();
		mapper.setDefaultPropertyInclusion(Include.NON_NULL);
		ByteArrayOutputStream jsonOs = new ByteArrayOutputStream();
		mapper.writeValue(jsonOs, Result.success().data(expenses));
		
		zipOs.putNextEntry(new ZipEntry("snapex-expense.json"));		
		zipOs.write(jsonOs.toByteArray());
		zipOs.closeEntry();
		
		zipOs.close();
		
		log.debug("***Complete export*** {}",params);
		
		return baos.toByteArray();		
	}
	
	
	private SearchParams searchParams(String staffId, String expenseId,	Integer status,  Date submittedDate, String level) {
		SearchParams params = new SearchParams();
		
		if(staffId != null) {		
			params.setUsername(DigestUtils.md5DigestAsHex(staffId.getBytes()));
		}
		
		params.setExpenseId(expenseId);
		params.setStatus(status);
		params.setSubmittedDate(submittedDate);		
		params.setLevel(level);	
		
		return params;
	}
}